package jmine.tec.utils.sql;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import jmine.tec.utils.collection.impl.CopyOnWriteHashMap;

/**
 * {@link InvocationHandler} que faz forward dos metodos ou chama os metodos no objeto 'this'
 * 
 * @author takeshi
 */
public abstract class ProxyOverridingInvocationHandler implements InvocationHandler {

    private final Object delegate;

    private final Map<Method, Method> invocationMapping = new CopyOnWriteHashMap<Method, Method>();

    /**
     * C'tor
     * 
     * @param delegate Object o delegate
     */
    public ProxyOverridingInvocationHandler(Object delegate) {
        super();
        this.delegate = delegate;
    }

    /**
     * {@inheritDoc}
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method toCall = invocationMapping.get(method);
        if (toCall == null) {
            toCall = findInvocation(method);
            invocationMapping.put(method, toCall);
        }
        if (toCall.getDeclaringClass().isInstance(this)) {
            try {
                return toCall.invoke(this, args);
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        } else {
            try {
                return toCall.invoke(this.getDelegate(), args);
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }
    }

    /**
     * @param method {@link Method}
     * @return {@link Method}
     */
    protected Method findInvocation(Method method) {
        try {
            Method probed = getClass().getMethod(method.getName(), method.getParameterTypes());
            if(method.getReturnType().isAssignableFrom(probed.getReturnType())) {
                return probed;
            }
            return method;
        } catch (NoSuchMethodException e) {
            return method;
        }
    }

    /**
     * @return the delegate
     */
    public Object getDelegate() {
        return delegate;
    }

}
